package com.sansan.qiangji.utils.plcUtils;

import com.sansan.qiangji.utils.DataUtil;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;

/**
 * 因为PLC连接数有限制，对象使用完后需调用disConnect方法释放PLC连接资源防止下次连不上PLC
 * */
@Slf4j
public class TDPlcUtil {
    private Socket socket;
    private String ip;
    private InputStream inputStream;
    private OutputStream outputStream;
    private SocketAddress socketAddress;
    private static final int CONNECT_TIMEOUT = 5000;
    private static final int RECEIVE_TIMEOUT = 5000;

    public TDPlcUtil(String ip, int port) {
        this.ip = ip;
        socketAddress = new InetSocketAddress(ip, port);
    }

    /**
     * 关闭连接
     * */
    public void disConnect() {
        if (socket != null) {
            try {
                socket.shutdownOutput();
                socket.shutdownInput();
                socket.close();
            } catch (IOException ignored) {
            }
            socket = null;
        }
    }
    /**
     * 判断是否连接，未连接则重新连接
     * */
    public Boolean isConnect() {
        if (socket == null) {
            try {
                socket = new Socket();
                socket.connect(socketAddress, CONNECT_TIMEOUT);
            } catch (Exception ex) {
                disConnect();
                log.error("【PLC通信】PLC连接超时，IP={}", ip);
                return false;
            }
        }
        try {
            socket.sendUrgentData(0xFF);
        } catch (Exception ex) {
            disConnect();
            log.error("【PLC通信】PLC断开连接，IP={}", ip);
            return false;
        }
        return true;
    }

    /**
     * 读寄存器(读取地址，读取个数)
     * */
    public Integer[] readStorage(int address, int count) {
        if (!isConnect()) {
            return null;
        }
        byte[] data = new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00};
        byte[] addressBytes = DataUtil.int2byte(address * 2);
        byte[] countBytes = DataUtil.int2byte(count * 2);
        // 写地址跟个数的byte值
        data[8] = addressBytes[0];
        data[9] = addressBytes[1];
        data[10] = countBytes[0];
        data[11] = countBytes[1];
        try {
            outputStream = socket.getOutputStream();
            outputStream.write(data);
            outputStream.flush();
        } catch (Exception ex) {
            disConnect();
            log.error("【PLC通信】发送数据失败，IP={}", ip);
            return null;
        }
        byte[] receiveData = new byte[1024];
        try {
            socket.setSoTimeout(RECEIVE_TIMEOUT);
            inputStream = socket.getInputStream();
            int i = inputStream.read(receiveData);
            // i为回复数据长度，正常数据不会小于10
            if (i < 10) {
                disConnect();
                log.error("【PLC通信】PLC未回复正常数据，IP={}", ip);
                return null;
            }
        } catch (Exception ex) {
            disConnect();
            log.error("【PLC通信】接收数据超时，IP={}", ip);
            return null;
        }
        Integer[] result = new Integer[count];
        for (int j = 0; j < count; j++) {
            // 将接受的byte，4个为一组转为二进制再拼接，最后转为ini
            String tString1 = Integer.toBinaryString((receiveData[j * 4 + 9] & 0xFF) + 0x100).substring(1);
            String tString2 = Integer.toBinaryString((receiveData[j * 4 + 10] & 0xFF) + 0x100).substring(1);
            String tString3 = Integer.toBinaryString((receiveData[j * 4 + 11] & 0xFF) + 0x100).substring(1);
            String tString4 = Integer.toBinaryString((receiveData[j * 4 + 12] & 0xFF) + 0x100).substring(1);
            String tString = tString3 + tString4 + tString1 + tString2;
            result[j] = Integer.parseUnsignedInt(tString, 2);
        }
        return result;
    }

    /**
     * 写寄存器(写入地址，写入值数组)
     * */
    public boolean writeStorage(int address, int[] values) {
        if (!isConnect()) {
            return false;
        }
        // 拼装data数据
        byte[] data = new byte[values.length * 4 + 13];
        data[0] = 0x00;
        data[1] = 0x00;
        data[2] = 0x00;
        data[3] = 0x00;
        int length = values.length * 4 + 7;
        byte[] lengthBytes = DataUtil.int2byte(length);
        data[4] = lengthBytes[0];
        data[5] = lengthBytes[1];
        data[6] = 0x01;
        data[7] = 0x10;
        byte[] addressBytes = DataUtil.int2byte(address * 2);
        data[8] = addressBytes[0];
        data[9] = addressBytes[1];
        byte[] countBytes = DataUtil.int2byte(values.length * 2);
        data[10] = countBytes[0];
        data[11] = countBytes[1];
        countBytes = DataUtil.int2byte(values.length * 4);
        data[12] = countBytes[1];
        for (int i = 0; i < values.length; i++) {
            // 将int转二进制字符串再分割为4个byte最后拼接到data里面
            String tString = String.format("%32s", Integer.toBinaryString(values[i])).replaceAll(" ", "0");
            String tString1 = tString.substring(16, 24);
            String tString2 = tString.substring(24);
            String tString3 = tString.substring(0, 8);
            String tString4 = tString.substring(8, 16);
            data[i * 4 + 13] = DataUtil.bit2byte(tString1);
            data[i * 4 + 14] = DataUtil.bit2byte(tString2);
            data[i * 4 + 15] = DataUtil.bit2byte(tString3);
            data[i * 4 + 16] = DataUtil.bit2byte(tString4);
        }
        try {
            outputStream = socket.getOutputStream();
            outputStream.write(data);
            outputStream.flush();
        } catch (Exception ex) {
            disConnect();
            log.error("【PLC通信】发送数据失败，IP={}", ip);
            return false;
        }
        byte[] receiveData = new byte[1024];
        try {
            socket.setSoTimeout(RECEIVE_TIMEOUT);
            inputStream = socket.getInputStream();
            int i = inputStream.read(receiveData);
            if (i < 10) {
                disConnect();
                log.error("【PLC通信】PLC未回复正常数据，IP={}", ip);
                return false;
            }
        } catch (Exception ex) {
            disConnect();
            log.error("【PLC通信】接收数据超时，IP={}", ip);
            return false;
        }
        return true;
    }

    /**
     * 读位状态
     * address比较特殊是传字符串"1.5"格式的参数，1代表高位地址，5代表低位地址且最高到7，到8的时候需进位
     * */
    public Boolean[] readBit(String address, int count) {
        if (!isConnect()) {
            return null;
        }
        byte[] data = new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00};
        String[] addressList = address.split("\\.");
        byte[] addressBytes = DataUtil.int2byte(40960 + Integer.parseInt(addressList[0]) * 8 + Integer.parseInt(addressList[1]));
        byte[] countBytes = DataUtil.int2byte(count);
        data[8] = addressBytes[0];
        data[9] = addressBytes[1];
        data[10] = countBytes[0];
        data[11] = countBytes[1];
        try {
            outputStream = socket.getOutputStream();
            outputStream.write(data);
            outputStream.flush();
        } catch (Exception ex) {
            disConnect();
            log.error("【PLC通信】发送数据失败，IP={}", ip);
            return null;
        }
        byte[] receiveData = new byte[1024];
        try {
            socket.setSoTimeout(RECEIVE_TIMEOUT);
            inputStream = socket.getInputStream();
            int i = inputStream.read(receiveData);
            if (i < 10) {
                disConnect();
                log.error("【PLC通信】PLC未回复正常数据，IP={}", ip);
                return null;
            }
        } catch (Exception ex) {
            disConnect();
            log.error("【PLC通信】接收数据超时，IP={}", ip);
            return null;
        }
        Boolean[] result = new Boolean[count];
        for (int j = 0; j < receiveData[8]; j++) {
            for (int k = 0; k < 8; k++) {
                if (j * 8 + k >= count) {
                    break;
                }
                String tString = Integer.toBinaryString((receiveData[9 + j] & 0xFF) + 0x100).substring(1);
                result[j * 8 + k] = tString.charAt(7 - k) == '1';
            }
        }
        return result;
    }
    /**
     * 写位状态
     * address比较特殊是传字符串"1.5"格式的参数，1代表高位地址，5代表低位地址且最高到7，到8的时候需进位
     * */
    public boolean writeBit(String address, Boolean[] values) {
        if (!isConnect()) {
            return false;
        }
        byte[] data = new byte[(int) Math.ceil((double) values.length / 8) + 13];
        data[0] = 0x00;
        data[1] = 0x00;
        data[2] = 0x00;
        data[3] = 0x00;
        int length = (int) Math.ceil((double) values.length / 8) + 7;
        byte[] lengthBytes = DataUtil.int2byte(length);
        data[4] = lengthBytes[0];
        data[5] = lengthBytes[1];
        data[6] = 0x01;
        data[7] = 0x0F;
        String[] addressList = address.split("\\.");
        byte[] addressBytes = DataUtil.int2byte(40960 + Integer.parseInt(addressList[0]) * 8 + Integer.parseInt(addressList[1]));
        data[8] = addressBytes[0];
        data[9] = addressBytes[1];
        byte[] countBytes = DataUtil.int2byte(values.length);
        data[10] = countBytes[0];
        data[11] = countBytes[1];
        countBytes = DataUtil.int2byte((int) Math.ceil((double) values.length / 8));
        data[12] = countBytes[1];
        for (int j = 0; j < (int) Math.ceil((double) values.length / 8); j++) {
            StringBuilder tString = new StringBuilder();
            for (int k = 7; k >= 0; k--) {
                if (j * 8 + k < values.length && values[j * 8 + k]) {
                    tString.append("1");
                } else {
                    tString.append("0");
                }
            }
            data[13 + j] = DataUtil.bit2byte(tString.toString());
        }
        try {
            outputStream = socket.getOutputStream();
            outputStream.write(data);
            outputStream.flush();
        } catch (Exception ex) {
            disConnect();
            log.error("【PLC通信】发送数据失败，IP={}", ip);
            return false;
        }
        byte[] receiveData = new byte[1024];
        try {
            socket.setSoTimeout(RECEIVE_TIMEOUT);
            inputStream = socket.getInputStream();
            int i = inputStream.read(receiveData);
            if (i < 10) {
                disConnect();
                log.error("【PLC通信】PLC未回复正常数据，IP={}", ip);
                return false;
            }
        } catch (Exception ex) {
            disConnect();
            log.error("【PLC通信】接收数据超时，IP={}", ip);
            return false;
        }
        return true;
    }
}
